﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using CodeCamp.Controllers;
using CodeCamp.Infrastructure;
using System.Configuration;
using MIMW = Microsoft.IdentityModel.Web;
using MIMT = Microsoft.IdentityModel.Tokens;
using SIMT = System.IdentityModel.Tokens;
using SSP = System.Security.Principal;
using SWS = System.Web.Security;
using RLI = RGoodSW.Logger.Interfaces;
using RGoodSW.Logger.MvcHelper.Extensions; //Extension methods
#if USE_WIF
using Microsoft.IdentityModel.Web.Configuration;
#endif
using MIC = Microsoft.IdentityModel.Claims;

namespace CodeCamp
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

  public class MvcApplication : System.Web.HttpApplication
  {
    internal static int _endRequestCount = 0;
    internal static int _errorCount = 0;

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute
        {
            ExceptionType = typeof(Exception),
            Master = "_Layout4Error",
            View = "GeneralError",
            Order = 99
        });
        filters.Add(new HandleErrorAttribute());
    }

    public static void RegisterRoutes( RouteCollection routes )
    {
      string defaultEventName = new Infrastructure.DefaultValueProvider()
             .GetDefaultStringValue(Infrastructure.Constants.Tags.defaultEventShortName, Infrastructure.Constants.Defaults.defaultEventShortName);

      routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
      routes.IgnoreRoute( "favicon.ico" );
      routes.IgnoreRoute("{*staticfile}", new { staticfile = @".*\.(css|js|gif|jpg|png)(/.*)?" });

#if false
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
#else
            routes.MapRoute(
              "RouteWithDefaultIndex",                    // route name
              "{eventName}/{controller}/{action}/{id}",   // URL pattern with parameters
              new                                         // parameter defaults
              {
                  eventName = defaultEventName,
                  controller = "Home",
                  action = "Index",
                  id = string.Empty
              },
              new                                         // constraints
              {
                  controller = "Home|JointEvent"
              });

            routes.MapRoute(
              "RouteWithDefaultList",                     // route name
              "{eventName}/{controller}/{action}/{id}",   // URL pattern with parameters
              new                                         // parameter defaults
              {
                  eventName = defaultEventName,
                  action = "List",
                  id = string.Empty
              });
#endif

    }

    //************
    #region Logger

    private static RLI.ILoggerExtended _loggerSingleton = null;
    public RLI.ILoggerExtended Logger
    {
        get
        {
            if (_loggerSingleton == null)
            {
                _loggerSingleton = DependencyResolver.Current.GetService<RLI.ILoggerExtended>();
            }
            return _loggerSingleton;
        }
    }

    #endregion //Logger

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
        Bootstrapper.Initialize();

        this.InitializeLogger(Logger);
        Logger.SetThreadContextData(this.GetLogContextDataForApp_Start());
        Logger.Info("App started");
        _endRequestCount = 0;
        _errorCount = 0;

#if USE_WIF
        FederatedAuthConfig.RegisterFederatedAuth(OnServiceConfigurationCreated);
#endif
    }

#if true //USE_WIF

    /// <summary>
    /// Implement a sliding window for authentication
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <remarks>
    /// http://blogs.msdn.com/b/vbertocci/archive/2010/06/16/warning-sliding-sessions-are-closer-than-they-appear.aspx
    /// http://stackoverflow.com/questions/5821351/how-to-set-sliding-expiration-in-my-mvc-app-that-uses-sts-wif-for-authenticati
    /// </remarks>
    protected void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, MIMW.SessionSecurityTokenReceivedEventArgs e)
    {
        var sessionToken = e.SessionToken;
        SIMT.SymmetricSecurityKey symmetricSecurityKey = null;

        if (sessionToken.SecurityKeys != null)
            symmetricSecurityKey = sessionToken.SecurityKeys.OfType<SIMT.SymmetricSecurityKey>().FirstOrDefault();

        //Condition.Requires(symmetricSecurityKey, "symmetricSecurityKey").IsNotNull();
        if (symmetricSecurityKey != null)
        {

            if (sessionToken.ValidTo > DateTime.UtcNow)
            {
                var slidingExpiration = sessionToken.ValidTo - sessionToken.ValidFrom;

                var renewTime = sessionToken.ValidTo.AddMinutes(-1 * (slidingExpiration.Minutes / 2));
                if (renewTime < DateTime.UtcNow)
                {
                    e.SessionToken = new MIMT.SessionSecurityToken(
                                sessionToken.ClaimsPrincipal,
                                sessionToken.ContextId,
                                sessionToken.Context,
                                sessionToken.EndpointId,
                                slidingExpiration,
                                symmetricSecurityKey);

                    e.ReissueCookie = true;
                }
            }
            else
            {
                var sessionAuthenticationModule = (MIMW.SessionAuthenticationModule)sender;

                sessionAuthenticationModule.DeleteSessionTokenCookie();

                e.Cancel = true;
            }
        }
    }

    protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
    {
#if true
        CodeCamp.MvcApplication app = sender as CodeCamp.MvcApplication;
        if ((app != null) && (app.Request != null))
        {
            if ((HttpContext.Current.User != null) && (HttpContext.Current.User.Identity != null) && HttpContext.Current.User.Identity.IsAuthenticated)
            {
                MIC.ClaimsPrincipal claimsPrincipal = HttpContext.Current.User as MIC.ClaimsPrincipal;
                if ((claimsPrincipal != null) && claimsPrincipal.Identity.IsAuthenticated)
                {
                    string extension = System.IO.Path.GetExtension(app.Request.Url.AbsolutePath);
                    bool urlHasExtension = !string.IsNullOrWhiteSpace(extension);

                    //TODO: Revisit this if any handling of aspx pages is added
                    //Only set claims for requests without file extensions
                    if (!urlHasExtension)
                    {
                        string eventShortName = null;
                        if (app.Request.Url.Segments.Count() >= 2)
                        {
                            eventShortName = app.Request.Url.Segments[1];
                            if (!string.IsNullOrWhiteSpace(eventShortName) && (eventShortName[eventShortName.Length - 1] == '/'))
                            {
                                if (eventShortName.Length == 1)
                                    eventShortName = null;
                                else
                                    eventShortName = eventShortName.Substring(0, (eventShortName.Length - 1));
                            }
                        }
                        var transformer = new LocalClaimsManager();
                        transformer.EventShortName = eventShortName;
                        var newPrincipal = transformer.Authenticate(string.Empty, claimsPrincipal);
                        System.Threading.Thread.CurrentPrincipal = newPrincipal;
                        HttpContext.Current.User = newPrincipal;
                    }

                }
            }
        }
#else
        CodeCamp.MvcApplication app = sender as CodeCamp.MvcApplication;
        if ((app != null) && (app.Request != null))
        {
            if ((HttpContext.Current.User != null) && (HttpContext.Current.User.Identity != null) && HttpContext.Current.User.Identity.IsAuthenticated)
            {
                MIC.ClaimsPrincipal claimsPrincipal = HttpContext.Current.User as MIC.ClaimsPrincipal;
                if (claimsPrincipal != null)
                {
                    try
                    {
                        string extension = System.IO.Path.GetExtension(app.Request.Url.AbsolutePath);
                        bool urlHasExtension = !string.IsNullOrWhiteSpace(extension);

                        //TODO: Revisit this if any handling of aspx pages is added
                        //Only set claims for requests without file extensions
                        if (!urlHasExtension)
                        {
                            string eventShortName = null;
                            if (app.Request.Url.Segments.Count() >= 2)
                            {
                                eventShortName = app.Request.Url.Segments[1];
                                if (!string.IsNullOrWhiteSpace(eventShortName) && (eventShortName[eventShortName.Length - 1] == '/'))
                                {
                                    if (eventShortName.Length == 1)
                                        eventShortName = null;
                                    else
                                        eventShortName = eventShortName.Substring(0, (eventShortName.Length - 1));
                                }
                            }

                            using (CodeCampDataRepository ccdr = new CodeCampDataRepository())
                            {
                                ClaimsLocalHandler clh = new ClaimsLocalHandler(ccdr, new DefaultValueProvider() , eventShortName);
                                clh.SetApplicationClaims(claimsPrincipal, true);
                            }
                        }
#if DEBUG
                        else
                        {
                            int foo = 1;
                        }
#endif
                    }
                    catch (Exception ex)
                    {
                        LogLastException(ex);
                    }
                }
            }
        }
#endif
    }
#endif


    protected void Application_End()
    {
        //Use log context without Request here as well, since this occurs asynchronously
        Logger.SetThreadContextData(this.GetLogContextDataForApp_Start());
        Logger.InfoFormat("App End EndRequests={0}, Errors={1}", _endRequestCount, _errorCount);
        Logger.Flush();
        _endRequestCount = 0;
        _errorCount = 0;
    }

#if USE_MEMBERSHIP
    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
    {
        HttpCookie authCookie = Context.Request.Cookies[SWS.FormsAuthentication.FormsCookieName];
        if (authCookie != null)
        {
            SWS.FormsAuthenticationTicket authTicket = SWS.FormsAuthentication.Decrypt(authCookie.Value);
            string[] roles = authTicket.UserData.Split(new Char[] { '|' });
            SSP.GenericPrincipal userPrincipal
                = new SSP.GenericPrincipal(new SSP.GenericIdentity(authTicket.Name), roles);
            Context.User = userPrincipal;
        }
    }
#endif

    protected bool LogLastException(Exception ex)
    {
        if (ex != null)
        {
            if (ex.GetType() == typeof(System.Data.Entity.Validation.DbEntityValidationException))
            {
                Logger.SetThreadContextData(this.GetLogContextData());
                return LogLastException(ex as System.Data.Entity.Validation.DbEntityValidationException);
            }
            else if (ex.GetType() == typeof(System.Data.Entity.Infrastructure.DbUpdateException))
            {
                Logger.SetThreadContextData(this.GetLogContextData());
                return LogLastException(ex as System.Data.Entity.Infrastructure.DbUpdateException);
            }
            else if (ex.GetType() == typeof(System.Data.Entity.Infrastructure.DbUpdateConcurrencyException))
            {
                Logger.SetThreadContextData(this.GetLogContextData());
                return LogLastException(ex as System.Data.Entity.Infrastructure.DbUpdateConcurrencyException);
            }
        }
        return false;
    }

    protected bool LogLastException(System.Data.Entity.Infrastructure.DbUpdateException ex)
    {
        Logger.Fatal(string.Format("{0}: {1}", ex.GetType().Name, ex.Message), ex);
        return true;
    }

    protected bool LogLastException(System.Data.Entity.Infrastructure.DbUpdateConcurrencyException ex)
    {
        Logger.Fatal(string.Format("{0}: {1}", ex.GetType().Name, ex.Message), ex);
        return true;
    }


    protected bool LogLastException(System.Data.Entity.Validation.DbEntityValidationException ex)
    {
        System.Text.StringBuilder logMsg = new System.Text.StringBuilder();
        logMsg.AppendFormat("{1}: {2} {0}",
            Environment.NewLine,
            ex.GetType().Name,
            ex.Message);
        if (ex.EntityValidationErrors != null)
        {
            logMsg.AppendLine("\t-----");
            foreach (System.Data.Entity.Validation.DbEntityValidationResult result in ex.EntityValidationErrors)
            {
                logMsg.AppendFormat("\t{1} {0}",
                    Environment.NewLine,
                    result.Entry);
                if (result.ValidationErrors != null)
                {
                    foreach (System.Data.Entity.Validation.DbValidationError error in result.ValidationErrors)
                    {
                        logMsg.AppendFormat("\t\t{1}: {2} {0}",
                            Environment.NewLine,
                            error.PropertyName,
                            error.ErrorMessage);
                    }
                }
            }
        }
        Logger.Fatal(logMsg.ToString(), ex);
        return true;
    }

    protected void Application_Error()
    {
        _errorCount++;
        Exception lastException = Server.GetLastError();

        bool loggedHere = false;
        if (lastException != null)
        {
            loggedHere = LogLastException(lastException);
        }
        if (!loggedHere)
        {
            //Skip logging routing errors (They'll get picked up in Application_EndRequest)
            this.Application_Error(lastException, Logger, true);
        }
    }

    private static class ContentFileExtensions
    {
        internal const string css = ".css";
        internal const string gif = ".gif";
        internal const string ico = ".ico";
        internal const string js = ".js";
        internal const string png = ".png";
        internal const string jpg = ".jpg";
    }

    private string[] _contentFileExtensions = 
    {
        ContentFileExtensions.css,
        ContentFileExtensions.gif,
        ContentFileExtensions.ico,
        ContentFileExtensions.js,
        ContentFileExtensions.png,
        ContentFileExtensions.jpg
    };

    protected void Application_EndRequest()
    {
        _endRequestCount++;
        if (Context.Response.StatusCode == (int)HttpStatusCode.NotFound)
        {
            if (!this.Application_EndRequest_404_LogContentFileNotFound(Context, Logger, _contentFileExtensions))
            {
                Logger.SetThreadContextData(this.GetLogContextData());
                string requestExtension = this.GetRequestURLFileExtensionLower();
                if (!string.IsNullOrWhiteSpace(requestExtension))
                {
                    if (requestExtension == ".aspx")
                    {
                        //Do something different for aspx URL's!
                        Logger.WarnFormat("URL not found. {0}URL=\"{1}\" {0}Referrer=\"{2}\" {0}Browser={3} {0}",
                            Environment.NewLine,
                            Context.Request.Url.PathAndQuery,
                            (Context.Request.UrlReferrer == null) ? null : Context.Request.UrlReferrer.PathAndQuery,
                            GlobalExtensions.BrowserInfo2Log(Context.Request.Browser)
                            );
                    }
                    else
                    {
                        //And other URL's that end with an extension
                        Logger.WarnFormat("URL not found. {0}URL=\"{1}\" {0}Referrer=\"{2}\" {0}Browser={3} {0}",
                            Environment.NewLine,
                            Context.Request.Url.PathAndQuery,
                            (Context.Request.UrlReferrer == null) ? null : Context.Request.UrlReferrer.PathAndQuery,
                            GlobalExtensions.BrowserInfo2Log(Context.Request.Browser)
                            );
                    }
                    Logger.Flush();
                }
                else
                {
                    //
                    //   http://stackoverflow.com/questions/619895/how-can-i-properly-handle-404-in-asp-net-mvc
                    //
                    Response.Clear();

                    var rd = new RouteData();
                    //rd.DataTokens["area"] = "AreaName";//In case controller in another area
                    rd.Values["controller"] = "Errors";
                    rd.Values["action"] = "NotFound";

                    //TO DO - change to getting logger from IOC
                    IController c = new ErrorsController(Logger);
                    c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
                }
            }
        }
    }

#if USE_WIF
    #region Federated Identity

    void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
    {
        string wifConfig = FederatedAuthConfig.GetWIFConfigName(new DefaultValueProvider());
        Logger.InfoFormat("OnServiceConfigurationCreated: wifConfig=\"{0}\"", wifConfig);
        e.ServiceConfiguration = new Microsoft.IdentityModel.Configuration.ServiceConfiguration(wifConfig);
        MachineKeySessionSecurityTokenHandler handler = new MachineKeySessionSecurityTokenHandler();
        e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(handler);
    }

    #endregion //Federated Identity
#endif
  }
}